/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.data.redis;

import org.apache.commons.lang3.reflect.MethodUtils;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.tianyun.cloud.data.redis.annotation.RedisContainer;
import org.tianyun.cloud.data.redis.configuration.RedisContainerProperties;
import org.tianyun.cloud.registrar.BeanRegistrar;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;

/**
 * RedisConnectionFactory注册，扫描Rabbit自定义注解自动创建Bean
 *
 * @auther ebert_chan
 */
public class RedisContainerRegistrar extends BeanRegistrar {

    /*
     * @see org.tianyun.cloud.registrar.BeanRegistrar#registerBeanDefinitions()
     */
    @Override
    public void registerBeanDefinitions() {
        Set<String> basePackages = this.getComponentScanningPackages();
        // 注册RedisContainer
        this.registerRedisContainer(basePackages);
    }

    /**
     * 扫描@RedisContainer注解
     *
     * @param basePackages
     * @auther ebert_chan
     */
    public void registerRedisContainer(Set<String> basePackages) {
        for (String basePackage : basePackages) {
            registerRedisContainer(basePackage);
        }
    }

    private void registerRedisContainer(String basePackage) {
        Set<BeanDefinition> candidateComponents = this.findCandidateComponents(basePackage, Component.class);
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is not an interface
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                if (annotationMetadata.isInterface()) {
                    continue;
                }
                String className = annotationMetadata.getClassName();
                this.resolveRedisContainer(className);
            }
        }
    }

    private void resolveRedisContainer(String className) {
        Class<?> clazz = this.loadClass(className);
        Method[] methods = MethodUtils.getMethodsWithAnnotation(clazz, RedisContainer.class, false, true);
        if (methods == null) {
            return;
        }

        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            RedisContainer redisContainer = AnnotatedElementUtils.findMergedAnnotation(method, RedisContainer.class);
            Bean bean = method.getDeclaredAnnotation(Bean.class);

            if (!RedisContainerProperties.class.isAssignableFrom(method.getReturnType())) {
                throw new BeanCreationException("The @RedisContainer annotation must be defined on RedisContainerProperties bean.");
            }

            String beanName = null;
            if (bean != null) {
                beanName = this.getFirstValue(bean.value());
                if (!StringUtils.hasText(beanName)) {
                    beanName = this.getFirstValue(bean.name());
                }
            }
            if (!StringUtils.hasText(beanName)) {
                beanName = method.getName();
            }

            Map<String, Object> attributes = AnnotationUtils.getAnnotationAttributes(redisContainer);
            // 配置器
            BeanDefinitionRegistry registry = this.getBeanDefinitionRegistry();
            // @formatter:off
			BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisConnectionFactoryConfigurer.class)
					.addConstructorArgValue(beanName)
					.addConstructorArgValue(attributes)
                    .addConstructorArgValue(getResourceLoader());
			// @formatter:on
            BeanDefinition configurerBeanDefinition = builder.getBeanDefinition();
            String configurerBeanName = "redisConnectionFactoryConfigurer#" + i;
            registry.registerBeanDefinition(configurerBeanName, configurerBeanDefinition);
        }
    }

}
